# -*- coding:utf-8 -*-
"""
搜索缓存服务 - Redis优化版本
用于加速新商品判断，减少数据库查询
"""

from typing import List, Dict, Any, Optional
from sqlalchemy.ext.asyncio import AsyncSession
from module_admin.service.blacklist_service import BlacklistService
from module_admin.entity.do.search_record_do import SearchRecord
from sqlalchemy import select, desc
from datetime import datetime
from utils.log_util import logger


class SearchCacheServiceRedis:
    """
    搜索缓存服务（Redis优化版）
    
    性能优化：
    1. 使用 Redis Set 存储已见过的商品ID（快速判断是否存在）
    2. 使用 Redis String 存储最大 publish_time（快速判断新商品）
    3. 批量操作减少网络往返
    4. 异步保存到MySQL（不阻塞搜索流程）
    """
    
    @classmethod
    async def process_search_results(
        cls,
        db: AsyncSession,
        redis_client,
        client_id: str,
        keyword: str,
        raw_results: List[Dict[str, Any]],
        user_id: Optional[int] = None  # ✅ 新增：用户ID（用于Redis缓存隔离）
    ) -> Dict[str, Any]:
        """
        处理搜索结果（Redis优化版）
        
        流程：
        1. 黑名单过滤
        2. 排序（按 publish_time 降序）
        3. 从 Redis 获取最大 publish_time 和已见过的商品ID集合
        4. 筛选真正的新商品（严格基于 publish_time > max，与MySQL版本逻辑一致）
           ⚠️ 重要：Redis Set 仅用于性能优化，不用于判断新商品（避免闲鱼旧数据误判）
        5. 更新 Redis（Set + max_publish_time）
        6. 异步保存到MySQL（不阻塞）
        7. 返回处理后的结果
        
        Args:
            db: 数据库会话
            redis_client: Redis客户端
            client_id: 客户端ID
            keyword: 搜索关键词
            raw_results: 原始搜索结果
            
        Returns:
            {
                'keyword': 搜索关键词,
                'total': 总数,
                'filtered': 过滤后数量,
                'new_count': 新商品数量,
                'results': 处理后的结果列表,
                'new_items': 新商品列表（用于下单）
            }
        """
        if not raw_results:
            return {
                'keyword': keyword,
                'total': 0,
                'filtered': 0,
                'new_count': 0,
                'results': [],
                'new_items': []
            }
        
        total_count = len(raw_results)
        logger.info(f"[{client_id}] 开始处理搜索结果（Redis优化）: {keyword}, 原始数量={total_count}")
        
        # 1. 黑名单过滤（按用户隔离）
        filtered_results = await BlacklistService.filter_search_results(db, raw_results, user_id=user_id)
        filtered_count = len(filtered_results)
        
        if filtered_count == 0:
            logger.info(f"[{client_id}] 黑名单过滤后无结果")
            return {
                'keyword': keyword,
                'total': total_count,
                'filtered': filtered_count,
                'new_count': 0,
                'results': [],
                'new_items': []
            }
        
        # 2. 排序（按 publish_time 降序，position 升序）
        sorted_results = sorted(
            filtered_results,
            key=lambda x: (
                -(x.get('publish_time', 0) or 0),  # publish_time 降序
                x.get('position', 999999)  # position 升序
            )
        )
        
        # 3. 从 Redis 获取缓存数据（按user_id隔离）
        # ✅ Redis key格式：search:items:{user_id}:{keyword} 或 search:items:{keyword}（兼容旧数据）
        if user_id:
            redis_key_items = f"search:items:{user_id}:{keyword}"
            redis_key_max_time = f"search:max_time:{user_id}:{keyword}"
        else:
            # 兼容旧数据（没有user_id的情况）
            redis_key_items = f"search:items:{keyword}"
            redis_key_max_time = f"search:max_time:{keyword}"
        
        try:
            # 批量获取：最大发布时间 + 已见过的商品ID集合
            max_publish_time = await redis_client.get(redis_key_max_time)
            max_publish_time = int(max_publish_time) if max_publish_time else 0
            
            # 获取已见过的商品ID集合（使用 Set，O(1) 查找）
            seen_item_ids = await redis_client.smembers(redis_key_items)
            seen_item_ids = set(seen_item_ids) if seen_item_ids else set()
            
            # ✅ 添加详细日志，帮助调试新商品判断问题
            logger.info(f"[{client_id}] Redis缓存: max_publish_time={max_publish_time} (时间: {datetime.fromtimestamp(max_publish_time) if max_publish_time > 0 else 'N/A'}), seen_items={len(seen_item_ids)}")
        except Exception as e:
            logger.warning(f"[{client_id}] Redis查询失败，回退到数据库: {e}")
            # Redis失败，回退到数据库查询
            max_publish_time = await cls._get_max_publish_time_from_db(db, keyword)
            seen_item_ids = set()
        
        # 4. 筛选真正的新商品（只基于 publish_time > max 判断，与MySQL版本保持一致）
        # ⚠️ 重要：不能使用 item_id not in Set 作为判断条件，因为闲鱼可能返回不存在的旧数据
        # 正确的逻辑：新商品 = publish_time > max_publish_time（严格基于时间判断）
        truly_new_items = []
        new_item_ids_to_add = []
        max_new_publish_time = max_publish_time
        
        for item in sorted_results:
            item_id = item.get('item_id') or item.get('id') or ''
            item_publish_time = item.get('publish_time', 0) or 0
            
            if not item_id:
                continue
            
            # ✅ 判断是否为新商品：严格基于 publish_time > max_publish_time
            # Redis Set 只用于去重和性能优化，不用于判断新商品
            is_new = item_publish_time > max_publish_time
            
            # ✅ 添加详细日志，帮助调试为什么某些商品不是新商品
            if not is_new and item_publish_time > 0:
                logger.debug(f"[{client_id}] 商品不是新商品: item_id={item_id}, publish_time={item_publish_time} (时间: {datetime.fromtimestamp(item_publish_time) if item_publish_time > 0 else 'N/A'}), max_publish_time={max_publish_time} (时间: {datetime.fromtimestamp(max_publish_time) if max_publish_time > 0 else 'N/A'})")
            
            if is_new:
                # 新商品：添加到结果列表
                truly_new_items.append(item)
                
                # 如果这个商品ID不在Set中，添加到Set（用于后续快速判断，避免重复查询数据库）
                # 注意：即使商品ID在Set中，只要publish_time更新，仍然是新商品
                if item_id not in seen_item_ids:
                    new_item_ids_to_add.append(item_id)
                
                # 更新最大发布时间
                if item_publish_time > max_new_publish_time:
                    max_new_publish_time = item_publish_time
        
        new_count = len(truly_new_items)
        
        # 5. 更新 Redis（批量操作，高性能）
        if new_count > 0:
            logger.info(f"[{client_id}] 发现 {new_count} 个新商品（Redis优化）")
            
            try:
                # 批量添加到 Redis Set（用于性能优化：快速判断商品是否已见过）
                # 注意：Set 中的商品ID仅用于辅助判断，不影响新商品的判定逻辑
                if new_item_ids_to_add:
                    await redis_client.sadd(redis_key_items, *new_item_ids_to_add)
                    # 设置过期时间（7天，避免内存无限增长）
                    await redis_client.expire(redis_key_items, 7 * 24 * 3600)
                
                # 更新最大发布时间（这是判断新商品的关键）
                if max_new_publish_time > max_publish_time:
                    await redis_client.set(redis_key_max_time, max_new_publish_time)
                    await redis_client.expire(redis_key_max_time, 7 * 24 * 3600)
                
                logger.debug(f"[{client_id}] Redis更新成功: 新商品{new_count}个, 新增到Set的ID{len(new_item_ids_to_add)}个, max_time={max_new_publish_time}")
            except Exception as e:
                logger.warning(f"[{client_id}] Redis更新失败: {e}，仅保存到数据库")
            
            # 6. 异步保存到MySQL（不阻塞搜索流程）
            # 注意：这里使用异步任务，不会阻塞当前请求
            try:
                import asyncio
                asyncio.create_task(cls._save_to_mysql_async(db, truly_new_items, keyword))
            except Exception as e:
                logger.warning(f"[{client_id}] 创建异步保存任务失败: {e}")
        else:
            logger.debug(f"[{client_id}] 无新商品")
        
        # 7. 返回处理后的结果
        return {
            'keyword': keyword,
            'client_id': client_id,
            'total': total_count,
            'filtered': filtered_count,
            'new_count': new_count,
            'results': sorted_results,
            'new_items': truly_new_items,
            'max_publish_time': max_new_publish_time
        }
    
    @classmethod
    async def _get_max_publish_time_from_db(cls, db: AsyncSession, keyword: str) -> int:
        """从数据库获取最大发布时间（回退方案）"""
        try:
            query = select(SearchRecord.publish_time).where(
                SearchRecord.search_keyword == keyword,
                SearchRecord.del_flag == '0'
            ).order_by(desc(SearchRecord.publish_time)).limit(1)
            
            result = await db.execute(query)
            max_time = result.scalar_one_or_none()
            return int(max_time) if max_time else 0
        except Exception as e:
            logger.warning(f"从数据库获取max_publish_time失败: {e}")
            return 0
    
    @classmethod
    async def _save_to_mysql_async(cls, db: AsyncSession, new_items: List[Dict[str, Any]], keyword: str):
        """
        异步保存新商品到MySQL（不阻塞搜索流程）
        """
        try:
            for item in new_items:
                item_id = item.get('item_id') or item.get('id')
                if not item_id:
                    continue
                
                # 检查是否已存在（双重检查，避免Redis和MySQL不一致）
                query = select(SearchRecord).where(
                    SearchRecord.item_id == item_id,
                    SearchRecord.del_flag == '0'
                )
                result = await db.execute(query)
                existing_record = result.scalar_one_or_none()
                
                if existing_record:
                    # ✅ 已存在，更新最后发现时间和发布时间（如果新的发布时间更大）
                    existing_record.last_found_time = datetime.now()
                    
                    # ⚠️ 重要：如果数据库中已有该商品，但新的 publish_time 更大，需要更新
                    # 这可以处理商品被重新发布或发布时间更新的情况
                    new_publish_time = item.get('publish_time', 0) or 0
                    existing_publish_time = existing_record.publish_time or 0
                    
                    if new_publish_time > existing_publish_time:
                        # 发布时间更新了，更新数据库记录
                        existing_record.publish_time = new_publish_time
                        logger.debug(f"更新商品发布时间: item_id={item_id}, 旧时间={existing_publish_time}, 新时间={new_publish_time}")
                    
                    # 已存在的商品不再是新商品（因为已经处理过了）
                    existing_record.is_new = False
                else:
                    # 不存在，创建新记录
                    # ✅ 使用TEXT类型后，可以保存最大65,535字节的文本，对于商品标题完全足够
                    # TEXT类型会自动处理编码，无需手动截断（MySQL会自动处理）
                    title = item.get('title') or ''
                    # 仅作为极端保护：如果标题超过64KB（几乎不可能），记录警告
                    if title:
                        title_bytes = len(title.encode('utf-8'))
                        if title_bytes > 65535:
                            # 按字节截断（而不是字符），确保不超过TEXT类型的限制
                            title_encoded = title.encode('utf-8')[:65535]
                            title = title_encoded.decode('utf-8', errors='ignore')
                            logger.warning(f"商品标题过长已截断: item_id={item_id}, 原始长度={title_bytes}字节")
                    
                    new_record = SearchRecord(
                        item_id=item_id,
                        title=title,
                        price=item.get('price'),
                        seller_id=item.get('seller_id'),
                        seller_nick=item.get('seller_nick'),
                        location=item.get('location'),
                        publish_time=item.get('publish_time'),
                        position=item.get('position'),
                        image_url=item.get('image_url'),
                        tag_name=item.get('tagname') or item.get('tagName') or item.get('tag_name') or '',  # ✅ 添加标签
                        search_keyword=keyword,
                        is_new=True,
                        del_flag='0'
                    )
                    db.add(new_record)
            
            await db.commit()
            logger.debug(f"异步保存 {len(new_items)} 条新商品到MySQL成功")
        except Exception as e:
            logger.error(f"异步保存到MySQL失败: {e}")
            await db.rollback()
    
    @classmethod
    async def clear_cache(cls, redis_client, keyword: Optional[str] = None, user_id: Optional[int] = None):
        """
        清空缓存
        """
        try:
            if keyword:
                redis_key_items = f"search:items:{keyword}"
                redis_key_max_time = f"search:max_time:{keyword}"
                await redis_client.delete(redis_key_items, redis_key_max_time)
            else:
                # 清空所有搜索缓存
                pattern = "search:items:*"
                keys = await redis_client.keys(pattern)
                if keys:
                    await redis_client.delete(*keys)
                pattern = "search:max_time:*"
                keys = await redis_client.keys(pattern)
                if keys:
                    await redis_client.delete(*keys)
        except Exception as e:
            logger.error(f"清空Redis缓存失败: {e}")

